(function() {
    function ipfocus(e) {
        if (e.key == `Enter`)
            this.$ipt.focus();
    }

    function inputValue(e) { //监听input的按键事件
        if (e.key === `Enter`) {
            Todolist.self.add(this.value); //调用todolist对象的添加项目方法，将input的输入添加进去
            this.value = ``; //添加完后清空输入框
        }
    }

    function changeList(e) { //切换状态
        e.stopPropagation();
        if (this.checked) { //如果当前选中的项的checkbox状态是选中，则说明是todo放入done中的操作
            Todolist.self.switch($(this).parents(`li`).index(), `todo`, `done`, $(this).parents(`li`));
        } else { //如果当前选中的项的checkbox状态是未选中，则说明是要将done放入todo中的操作
            Todolist.self.switch($(this).parents(`li`).index(), `done`, `todo`, $(this).parents(`li`));
        }
    }

    function delLi(e) { //删除项目
        e.stopPropagation();
        if (confirm(`确定删除该项吗?`)) { //执行todolist对象的移除项目的方法
            Todolist.self.remove($(this).parents("li"), this.dataset.done, $(this).parents("li").index());
        }
    }

    function changeContent(lab, index, cate) { //修改input内容后保存数据
        const value = this.val(); //保存input中内容
        lab.append($(`<p>${value}</p>`)); //重新给label标签添加p标签，并把新内容放进去
        this.remove(); //移除用于修改的input
        Todolist.self[cate].data[index].title = value; //将对应的数组的title修改为新的数据
        Todolist.local.saveData(cate, Todolist.self[cate].data); //保存到本地
    }

    function modiLi(e) { //点击li时的修改事件
        if (e.target.nodeName === `LABEL` || e.target.nodeName === `P`) return; //保持label的功能，只有在label之外的区域触发修改事件
        const $this = $(this);
        $this.find(`input[type="checkbox"]`)[0].checked ? Todolist.temp = `done` : Todolist.temp = `todo`; //找到鼠标点击对应的数据区是未完成区还是已完成区
        Todolist.self.modi($this.find("p"), $this.index()); //调用todolist的修改方法
    }
    class Item { //列表项对象
        constructor(title, done) {
            this.title = title; //title属性保存项目的文字内容
            this.done = done; //done属性保存项目的状态是完成还是未完成，完成是true，未完成是false
        }
    }
    class Todolist {
        constructor(ipt, todo, done) { //ipt传入的是输入表单input，todo传入的是未完成列表ul，done传入的是完成列表ul
            Todolist.self = this; //该静态属性保存当前的todolist对象的地址
            Todolist.temp = null; //在下面的方法中，该静态属性多用来保存当前操作的是完成事项还是未完成事项
            //error静态属性用来保存需要抛出的错误信息
            Todolist.error = [`输入不能为空！`, `删除项目出错！`, `计数出错！`, `切换状态出错！`, `编辑出错！`];
            //local静态属性用来保存本地储存对象，用于执行本地储存操作
            Todolist.local = new localData();
            this.todo = {}; //todo对象用于把保存的项目数组交给模版引擎渲染
            this.done = {}; //done对象同上，只是一个保存未完成，一个保存已完成
            this.todo.data = Todolist.local.loadData(`todo`); //列表项-未完成项数组
            this.done.data = Todolist.local.loadData(`done`); //列表项-已完成项数组
            this.$ipt = $(ipt).on(`keyup`, inputValue); //为输入框绑定按键事件，触发上面写的添加函数
            //为未完成的ul列表委托点击checkbox和点击删除按钮span的事件，使用了链式调用。分别触发交换列表事件和删除li的事件。
            this.$todo = $(todo).on(`click`, `input[type="checkbox"]`, changeList).on(`click`, `span.del`, delLi).on(`click`, `li`, modiLi);
            //为已完成的ul列表委托同上的事件
            this.$done = $(done).on(`click`, `input[type="checkbox"]`, changeList).on(`click`, `span.del`, delLi).on(`click`, `li`, modiLi);
            //为页面绑定监听按键事件，目的是按enter键使输入框获取焦点
            $(document).on(`keyup`, ipfocus.bind(this));
        }
        render(done) { //将状态为done的项目渲染到对应的ul上
            //判断传入的参数是true还是false，如果是true，说明将要渲染的应该是属性名为done的ul（即this.done），反之，说明将要渲染的是属性名为todo的ul（即this.todo）
            //前面构造函数中初始化了两个数组，分别叫this.todo.data和this.done.data
            done ? Todolist.temp = `done` : Todolist.temp = `todo`;
            //模版字符串`\$${Tdolist.temp}`的结果是一个字符串‘$todo’或者'$done'，取决于Todolist.temp的值是'todo'还是'done'
            //用模版引擎渲染对象this.todo或者this.done，模版中写的是each data，即遍历对象中的data数组，把数组中的元素渲染到页面上
            this[`\$${Todolist.temp}`].html(template(`tpl-li`, this[Todolist.temp]));
            //计数的方法，传入的第一个参数是当前渲染对应的计数模块的span的jq对象，第二个参数是当前渲染的列表数组的长度，即需要显示的数字
            this.listNum(this[`\$${Todolist.temp}`].prev().children(`span.count-span`), this[Todolist.temp].data.length);
        }
        add(value) { //添加内容为value的元素到未完成事项ul里
            try {
                if (!value.trim()) throw new Error(Todolist.error[0]); //如果内容为空则不添加
                const item = new Item(value, false); //创建一个事项的对象
                this[`todo`].data.unshift(item); //将对象添加到待完成事项数组中
                Todolist.local.saveData(`todo`, this[`todo`].data); //将待完成事项添加到本地储存中
                this.render(false);
            } catch (e) {
                alert(e.message); //抛出错误
            }
        }
        remove(item, cate, index) { //移除item事项，其完成状态是cate，该事项在数组中的索引是index
            try {
                if (item) {
                    cate === `true` ? Todolist.temp = `done` : Todolist.temp = `todo`;
                    this[`${Todolist.temp}`].data.splice(index, 1); //删除ul中索引为index的项
                    Todolist.local.saveData(Todolist.temp, this[`${Todolist.temp}`].data); //将对应的数组保存到本地
                    item.remove(); //在ul中移除该事项
                    this.listNum(this[`\$${Todolist.temp}`].prev().children(`span.count-span`), this[Todolist.temp].data.length); //对应数组计数
                } else throw new Error(Todolist.error[1]);
            } catch (e) {
                alert(e.message);
            }
        }
        listNum(count, len) { //计数
            try {
                if (len >= 0) return count.text(len);
                throw new Error(Todolist.error[2]); //如果当前数组长度是负数，即要显示的数是负数，说明出错了，抛出错误
            } catch (e) {
                alert(e.message);
            }
        }
        switch (index, head, tail, item) {
            try {
                if (index >= 0) {
                    let op = this[head].data[index]; //保存当前操作数组，方便后面使用
                    op.done = !op.done; //将数据的完成状态更改
                    this[tail].data.push(op); //将数据放入另一个状态的数组
                    this[head].data.splice(index, 1); //在原数组中删除该数据
                    let cl = $(item).find("span.del"); //找到删除按钮，因为该按钮上也记录了数据的完成状态，需要对应更改
                    cl[0].dataset.done === `true` ? cl[0].dataset.done = `false` : cl[0].dataset.done = `true`; //由于是字符串，因此不能使用布尔值操作，需要判断一下
                    this[`\$${tail}`].append(item); //将元素移入另一个ul
                    Todolist.local.saveData(`todo`, this[`todo`].data); //保存本地数据
                    Todolist.local.saveData(`done`, this[`done`].data);
                    this.listNum(this[`\$${head}`].prev().children(`span.count-span`), this[head].data.length); //计数
                    this.listNum(this[`\$${tail}`].prev().children(`span.count-span`), this[tail].data.length);
                } else throw new Error(Todolist.error[3]);
            } catch (e) {
                alert(e.message);
            }
        }
        modi(item, index) { //修改内容的方法
            try {
                if (!item) throw new Error(Todolist.error[4]);
                let value = item.text(); //获取需要修改的元素的内容
                const lab = item.parent(); //获取装内容的p标签的父元素label
                item.remove(); //将p元素移除
                const newIpt = $(`<input class="modi" value="${value}" />`);
                lab.append(newIpt); //替换为可修改的input框
                newIpt.on(`blur`, changeContent.bind(newIpt, lab, index, Todolist.temp)); //为input框添加失去焦点事件，失去焦点保存数据，为函数传参数
                newIpt.focus().select(); //使新input框获取焦点并全选
            } catch (e) {
                alert(e.message);
            }

        }
    }
    $(function() {
        let todo = new Todolist($("#todolist")[0], $(".today-item .item-list")[0], $(".done-item .item-list")[0]);
        //调用todolist的render渲染事件,参数为true代表渲染已完成列表，参数为false代表渲染未完成列表
        todo.render(true);
        todo.render(false);
        $("#clearAll").on(`click`, function() { //clear按钮清除所有事项
            localStorage.removeItem(`todo`);
            localStorage.removeItem(`done`);
            location.reload();
        })
    })
})()